/*******************************************************************************
 * Copyright (c) 2011 Irineu Martins de Lima Moura (imlm2).
 * This file is part of JBeat.
 * 
 *     JBeat is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU Lesser General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 * 
 *     JBeat is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU Lesser General Public License for more details.
 * 
 *     You should have received a copy of the GNU Lesser General Public License
 *     along with JBeat.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Contributors:
 *     Irineu Martins de Lima Moura (imlm2) - initial API and implementation
 ******************************************************************************/
package br.ufpe.cin.imlm2.jbeat;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import soot.Pack;
import soot.PackManager;
import soot.SceneTransformer;
import soot.Transform;
import br.ufpe.cin.imlm2.jbeat.soot.dump.ClassDumper;
import br.ufpe.cin.imlm2.jbeat.soot.icpoints.TrapInspector;
import br.ufpe.cin.imlm2.jbeat.soot.report.ExceptionReporter;
import br.ufpe.cin.imlm2.jbeat.soot.throwableset.generation.GeneratesCalculator;
import br.ufpe.cin.imlm2.jbeat.soot.throwableset.generation.RaisesCalculator;
import br.ufpe.cin.imlm2.jbeat.soot.throwableset.propagation.PropagatesCalculator;
import br.ufpe.cin.imlm2.jbeat.soot.traps.EmptyTrapRemover;
import br.ufpe.cin.imlm2.util.BuildClasspath;
import br.ufpe.cin.imlm2.util.SystemProperties;
import br.ufpe.cin.imlm2.util.options.IOptions;
import br.ufpe.cin.imlm2.util.options.NoSuchOptionException;
import br.ufpe.cin.imlm2.util.options.Option;
import br.ufpe.cin.imlm2.util.options.Parameterizable;
import br.ufpe.cin.imlm2.util.options.ParsingException;

public class Main {

	private static final SceneTransformer EMPTY_TRAP_REMOVER = new EmptyTrapRemover();
	private static final SceneTransformer TRAP_INSPECTOR = new TrapInspector();
	private static final RaisesCalculator RAISES_CALCULATOR = new RaisesCalculator();
	private static final GeneratesCalculator GENERATES_CALCULATOR = new GeneratesCalculator();
	private static final PropagatesCalculator PROPAGATES_CALCULATOR = new PropagatesCalculator();
	private static final ExceptionReporter EXCEPTION_REPORTER = new ExceptionReporter();
	private static final ClassDumper CLASS_DUMPER = new ClassDumper();

	private static final Transform EMPTY_TRAP_REMOVER_TRANSFORM = new Transform( "wjtp.etr",
			EMPTY_TRAP_REMOVER );
	private static final Transform TRAP_INSPECTOR_TRANSFORM = new Transform( "wjtp.ti",
			TRAP_INSPECTOR );
	private static final Transform RAISES_CALCULATOR_TRANSFORM = new Transform( "wjtp.rc",
			RAISES_CALCULATOR );
	private static final Transform GENERATES_CALCULATOR_TRANSFORM = new Transform( "wjtp.gc",
			GENERATES_CALCULATOR );
	private static final Transform PROPAGATES_CALCULATOR_TRANSFORM = new Transform( "wjtp.pc",
			PROPAGATES_CALCULATOR );
	private static final Transform EXCEPTION_REPORTER_TRANSFORM = new Transform( "wjtp.er",
			EXCEPTION_REPORTER );
	private static final Transform CLASS_DUMPER_TRANSFORM = new Transform( "wjtp.dc", CLASS_DUMPER );

	private static final List<Transform> TRANSFORMS = new ArrayList<Transform>();
	static {
		TRANSFORMS.add( EXCEPTION_REPORTER_TRANSFORM );
		TRANSFORMS.add( GENERATES_CALCULATOR_TRANSFORM );
		TRANSFORMS.add( PROPAGATES_CALCULATOR_TRANSFORM );
		TRANSFORMS.add( RAISES_CALCULATOR_TRANSFORM );
		TRANSFORMS.add( CLASS_DUMPER_TRANSFORM );
	}

	public static void main( String[] args ) throws ParsingException, NoSuchOptionException {
		registerParserOptions();
		JBeatOptions parser = JBeatOptions.getInstance();
		List<String> unparsed = parser.parse( args );
		if ( unparsed.size() == 1 ) {

			registerTransformersOptions();

			Pack wjtpPack = PackManager.v().getPack( "wjtp" );

			wjtpPack.add( EMPTY_TRAP_REMOVER_TRANSFORM );

			if ( parser.isHandlerInspection() ) {
				wjtpPack.add( TRAP_INSPECTOR_TRANSFORM );
			}

			if ( parser.isAnalyzeRuntime() ) {
				wjtpPack.add( GENERATES_CALCULATOR_TRANSFORM );
			}

			wjtpPack.add( RAISES_CALCULATOR_TRANSFORM );

			wjtpPack.add( PROPAGATES_CALCULATOR_TRANSFORM );

			if ( parser.isDumpClasses() )
				wjtpPack.add( CLASS_DUMPER_TRANSFORM );

			wjtpPack.add( EXCEPTION_REPORTER_TRANSFORM );
			soot.Main.main( buildSootCommandLine( unparsed.get( 0 ) ).split( " " ) );

			if ( parser.isCleanSootOutput() )
				deleteSootOutput();

		} else {
			if ( !parser.isShowHelp() ) {
				System.out.println( "Invalid usage." );
				System.out.println( parser.getHelp() );
			}
		}

	}

	private static void registerParserOptions() {
		IOptions parserOps = JBeatOptions.getInstance();
		for ( Transform transform : TRANSFORMS ) {
			IOptions opts = ((Parameterizable) transform.getTransformer()).getOptions();
			for ( Option option : opts.getOptions() ) {
				if ( !option.getName().equals( "enabled" ) )
					parserOps.addOption( option );
			}
		}

	}

	private static void deleteSootOutput() {
		File sootOutput = new File( "sootOutput" );
		if ( sootOutput.exists() && sootOutput.isDirectory() ) {
			deleteDirectory( sootOutput );
		}

	}

	private static void deleteDirectory( File dir ) {
		for ( File f : dir.listFiles() ) {
			if ( f.isDirectory() )
				deleteDirectory( f );
			else
				f.delete();
		}
		dir.delete();
	}

	private static String buildTargetClassPath( List<String> cp ) {
		List<String> targetCP = cp;
		if ( SystemProperties.TARGET_LIBRARIES != null ) {
			String[] dirs = SystemProperties.TARGET_LIBRARIES.split( 
					SystemProperties.PATH_SEPARATOR );
			for ( String dir : dirs ) {
				File directory = new File( dir );
				targetCP.addAll( BuildClasspath.buildFromCPEntries( directory, false ) );

			}
		}
		StringBuffer classpath = new StringBuffer();
		for ( String path : targetCP ) {
			classpath.append( path ).append( SystemProperties.PATH_SEPARATOR );
		}
		if ( classpath.length() > 0 ) {
			classpath.delete( classpath.lastIndexOf( SystemProperties.PATH_SEPARATOR ),
					classpath.length() );
		}
		return classpath.toString();
	}

	private static void registerTransformersOptions() {
		for ( Transform t : TRANSFORMS ) {
			t.setDeclaredOptions( buildDeclareOptions( 
					((Parameterizable) t.getTransformer()).getOptions() ) );
		}
	}

	private static String buildDeclareOptions( IOptions opts ) {
		StringBuffer phaseOptions = new StringBuffer();
		for ( Option opt : opts.getOptions() ) {
			phaseOptions.append( opt.getName() ).append( ' ' );
		}
		if ( phaseOptions.length() > 0 )
			phaseOptions.deleteCharAt( phaseOptions.length() - 1 );
		return phaseOptions.toString();
	}

	private static String buildSootCommandLine( String mainClass ) throws NoSuchOptionException {
		JBeatOptions parser = JBeatOptions.getInstance();
		StringBuffer sootOptions = new StringBuffer();

		sootOptions.append( mainClass ).append( ' ' );

		// Main class
		sootOptions.append( "-main-class" ).append( ' ' ).append( mainClass ).append( ' ' );

		// General options
		sootOptions.append( "-w" ).append( ' ' );
		sootOptions.append( "-app" ).append( ' ' );
		sootOptions.append( "-output-format" ).append( ' ' ).append( 'J' ).append( ' ' );

		if ( parser.isVerbose() )
			sootOptions.append( "-verbose" ).append( ' ' );

		if ( parser.isTime() )
			sootOptions.append( "-time" ).append( ' ' );

		if ( parser.isAllowMissingDependencies() )
			sootOptions.append( "-allow-phantom-refs" ).append( ' ' );

		if ( SystemProperties.TARGET_LIBRARIES == null )
			sootOptions.append( "-prepend-classpath" ).append( ' ' );
		String cp = buildTargetClassPath( parser.getClasspath() );
		sootOptions.append( "-cp" ).append( ' ' ).append( cp ).append( ' ' );

		// Cg construction options
		sootOptions.append( "-p" )
				.append( ' ' )
				.append( "cg" )
				.append( ' ' )
				.append( "implicit-entry" )
				.append( ':' )
				.append( "false" )
				.append( ' ' );
		if ( parser.getOptionStringValue( "cg" ).equals( "spark" ) ) {
			sootOptions.append( "-p" )
					.append( ' ' )
					.append( "cg.spark" )
					.append( ' ' )
					.append( "enabled" )
					.append( ':' )
					.append( "true" )
					.append( ' ' );
			sootOptions.append( "-p" )
					.append( ' ' )
					.append( "cg.spark" )
					.append( ' ' )
					.append( "verbose" )
					.append( ':' )
					.append( "true" )
					.append( ' ' );
			sootOptions.append( "-p" )
					.append( ' ' )
					.append( "cg.spark" )
					.append( ' ' )
					.append( "force-gc" )
					.append( ':' )
					.append( "true" )
					.append( ' ' );
		}

		for (String appPack : parser.getApplicationPackages()) {
			sootOptions.append("-i").append(' ').append(appPack).append(' ');
		}
		
		for (String libPack : parser.getLibraryPackages()) {
			sootOptions.append("-x").append(' ').append(libPack).append(' ');
		}
		
		for ( Transform transform : TRANSFORMS ) {
			if ( PackManager.v().hasPhase( transform.getPhaseName() ) ) {
				IOptions opts = ((Parameterizable) transform.getTransformer()).getOptions();
				for ( Option option : opts.getOptions() ) {
					if ( !option.getName().equals( "enabled" ) )
						sootOptions.append( "-p" )
								.append( ' ' )
								.append( transform.getPhaseName() )
								.append( ' ' )
								.append( option.getName() )
								.append( ':' )
								.append( parser.getOptionStringValue( option.getName() ) )
								.append( ' ' );

				}
			}
		}
		System.err.println( "Soot command-line:" );
		System.err.println( sootOptions.toString() );
		return sootOptions.toString();
	}
}
